const hx = require('hbuilderx')
const { join, posix, basename, dirname } = require('path')
const findIcon = require('../../public/vscode/icon')
const fs = require('fs-extra')
const util = require('util')
const cache = require('../../utils/cache')
const moment = require('moment')
const { URL } = require('url')
const file = require('../../utils/file')
const viewManager = require('../../utils/view-manager')
const log = require('../../utils/log')
const progress = require('../../utils/progress')
const ReactiveManger = require('../../utils/ReactiveManger')
const os = require('../../utils/os')
const { disconnect } = require('../../utils/connection')
const compressing = require('compressing')

const pageIcons = new Set()
const reactive = new ReactiveManger()

async function onInit() {
    cache.tree.clear()
    pageIcons.clear()
    reactive.clear()
    reactive.onChanged = (target, changed) => {
        changed.path = target.path
        this.onFn('onChangeNode', changed)
    }
    let name = ''
    if (this.options.name && this.options.name != this.options.host) {
        name = this.options.name + ' ● '
    } else {
        name = ''
    }
    return {
        name: name + this.options.host,
        host: this.options.host
    }
}

async function onLoad({ path: parentPath, noUsedCache }) {
    if (!noUsedCache && cache.tree.has(parentPath)) {
        return cache.tree.get(parentPath)
    }
    const client = cache.connections.get(this.options.host)
    const items = await client.connection.readFolder(parentPath)
    const nodes = []
    const trashcanPath = this.options.connection.trashcanPath
    const symbolLinks = []
    for (const it of items) {
        let path = posix.join(parentPath, it.filename)
        let collapsibleState = 0
        let isFile = true
        let contextValue = 'file'
        let lastUpdateTime = it.attrs.mtime
        let size = it.attrs.size
        let label = it.filename
        let deleteTime = ''
        let source = ''
        let isDirectory = it.attrs.isDirectory()
        let name = it.filename
        let isSymbolLink = it.attrs.isSymbolicLink()
        if (isSymbolLink) {
            name = '_unknow_symbol_file'
        }
        if (isDirectory) {
            collapsibleState = 1
            isFile = false
            contextValue = 'folder'
        }
        if (name == 'docker-compose.yaml') {
            contextValue = 'docker-compose'
        }
        if (path == trashcanPath) {
            contextValue = 'trashcan'
        } else if (parentPath == trashcanPath) {
            contextValue = 'trashcan-file'
            const metedata = await this.options.connection.parseMetedata(path)
            label = basename(metedata.s)
            deleteTime = moment(metedata.d).format('YYYY-MM-DD HH:mm:ss')
            source = metedata.s
        } else if (path.startsWith(trashcanPath)) {
            contextValue = 'trashcan-file-children'
        }
        const icon = findIcon(name, isFile, isSymbolLink)
        loadIcon.call(this, icon)
        const item = reactive.ref({
            label,
            parentPath: parentPath,
            path: path,
            collapsibleState,
            isFile,
            icon: icon.id,
            contextValue,
            lastUpdateTime,
            host: this.options.host,
            root: this.options.root,
            size,
            deleteTime,
            source
        })
        if (isSymbolLink) {
            symbolLinks.push(item)
        }
        nodes.push(item)
    }
    nodes.sort((a, b) => a.label.localeCompare(b.label))
    symbolLinks.sort((a, b) => a.label.localeCompare(b.label))
    const data = {
        nodes,
        path: parentPath
    }
    cache.tree.set(parentPath, data)
    updateSymbolLink.call(this, client, symbolLinks)
    return data
}

async function updateSymbolLink(client, nodes) {
    for (const node of nodes) {
        reactive.begingBatch()
        const path = node.path
        let targetPath = await client.connection.symlinkTarget(path)
        if (targetPath) {
            try {
                let name = basename(targetPath)
                let stat = await client.connection.stat(targetPath)
                const icon = findIcon(name, !stat.isDirectory(), true)
                loadIcon.call(this, icon)
                node.collapsibleState = stat.isDirectory()
                node.icon = icon.id
                node.isFile = !stat.isDirectory()
            } catch (_) {}
        }
        reactive.commit()
    }
}

async function loadIcon(icon) {
    if (pageIcons.has(icon.id)) {
        return
    }
    const icons = [await icon.read('index.svg')]
    this.onFn('onIconLoad', icons)
    pageIcons.add(icon.id)
}

async function onContentChange() {
    cache.tree.clear()
    pageIcons.clear()
    reactive.clear()
    this.onFn('onInit')
}

async function onRemoveCache(path) {
    cache.tree.delete(path)
}

async function refresh(path) {
    const data = await onLoad.call(this, {
        path,
        noUsedCache: true
    })
    await this.onFn('onLoadResponse', data)
}

async function onCommand(options) {
    let data = options.data
    let response = true
    let tip
    let button
    const { host } = this.options
    const client = cache.connections.get(host)
    const conn = client.connection
    switch (options.command) {
        case 'BACKUP_TIMEPOINT':
            await conn.createTimePointBackup(data.path, data.isFile)
            break
        case 'BACKUP_VERSION':
            await conn.createBackupOfVersion(data.path, data.isFile)
            break
        case 'REMOVE':
            tip = util.format('确定彻底删除以下1个%s?', data.isFile ? '文件' : '目录')
            button = await hx.window.showMessageBox({
                type: 'question',
                title: tip,
                text: data.path,
                buttons: ['彻底删除', '取消']
            })
            if (button == '彻底删除') {
                await conn.remove(data.path)
            }
            break
        case 'REMOVE_TO_TRASHCAN':
            tip = util.format('确定删除以下1个%s到回收站?', data.isFile ? '文件' : '目录')
            button = await hx.window.showMessageBox({
                type: 'question',
                title: tip,
                text: data.path,
                buttons: ['删除', '取消']
            })
            if (button == '删除') {
                await conn.removeToTrashcan(data?.path)
                const node = await onLoad.call(this, {
                    path: client.connection.trashcanPath,
                    noUsedCache: true
                })
                await this.postMessage({
                    command: 'onLoadResponse',
                    data: node
                })
            }
            break
        case 'REMOVE_NO_CONFIRM':
            {
                await conn.removeToTrashcan(data?.path)
                const node = await onLoad.call(this, {
                    path: client.connection.trashcanPath,
                    noUsedCache: true
                })
                await this.postMessage({
                    command: 'onLoadResponse',
                    data: node
                })
            }
            break
        case 'RESTORE_TRASHCAN_ITEM':
            {
                button = await hx.window.showMessageBox({
                    type: 'question',
                    title: '请选择同名文件的合并策略?',
                    text: util.format('%s 合并至 %s', data.path, data.source),
                    buttons: ['全部覆盖', '备份并覆盖', '由我决定', '取消']
                })
                switch (button) {
                    case '全部覆盖':
                        await conn.move(data.path, data.source)
                        break
                    case '备份并覆盖':
                        await conn.merge(data.path, data.source, async item => {
                            await conn.createBackupOfVersion(item.target, item.sourceStat.isFile())
                            await conn.move(item.source, item.target)
                        })
                        break
                    case '由我决定':
                        await conn.merge(data.path, data.source, async item => {
                            button = await hx.window.showMessageBox({
                                type: 'question',
                                title: '请选择此文件的合并策略合并策略?',
                                text: item.target,
                                buttons: ['覆盖', '备份并覆盖', '跳过', '取消']
                            })
                            switch (button) {
                                case '覆盖':
                                    await conn.move(item.path, item.source)
                                    break
                                case '备份并覆盖':
                                    await conn.createBackupOfVersion(
                                        item.target,
                                        item.sourceStat.isFile()
                                    )
                                    await conn.move(item.source, item.target)
                                    break
                                case '取消':
                                    return -1
                            }
                        })
                        break
                    case '全部跳过':
                        await conn.merge(data.path, data.source)
                        break
                }
                const node = await onLoad.call(this, {
                    path: dirname(data.source),
                    noUsedCache: true
                })
                await this.postMessage({
                    command: 'onLoadResponse',
                    data: node
                })
            }
            break
        case 'REMOVE_TRASHCAN_FILE':
            tip = util.format('确定删除以下1个回收站中的%s?', data.isFile ? '文件' : '目录')
            button = await hx.window.showMessageBox({
                type: 'question',
                title: tip,
                text: data.label,
                buttons: ['彻底删除', '取消']
            })
            if (button == '彻底删除') {
                await conn.remove(data.path)
            }
            break
        case 'CLEAR_TRASHCAN':
            tip = util.format('确定清空回收站?')
            button = await hx.window.showMessageBox({
                type: 'question',
                title: tip,
                text: '将彻底删除回收站中的文件, 此操作不可逆, 请小心操作!',
                buttons: ['彻底删除', '取消']
            })
            if (button == '彻底删除') {
                cache.tree.delete('/trashcan')
                await conn.removeContent(data.path)
            }
            break
        case 'CREATE_FILE':
            {
                const desc = util.format(
                    '目标目录: %s\n将在此目录创建新的文件, 请输入文件名称',
                    data.path
                )
                const name = await file.createFile('新建文件', desc, '文件名称')
                if (name) {
                    const remoteFile = posix.join(data.path, name)
                    await conn.writeText(remoteFile, '')
                    const clientCacheFile = conn.localCachePath(remoteFile)
                    const logs = progress.Download.File()
                    await conn.checkOrUpdateFileCache(remoteFile, logs.onProgress)
                    await hx.workspace.openTextDocument(clientCacheFile)
                    await refresh.call(this, data.path)
                }
            }
            break
        case 'CREATE_PATH':
            {
                const desc = util.format(
                    '目标目录: %s\n将在此目录创建新的子目录, 请输入子目录名称',
                    data.path
                )
                const name = await file.createFile('新建目录', desc, '目录名称')
                if (name) {
                    const remotePath = posix.join(data.path, name)
                    await conn.mkdir(remotePath)
                    await refresh.call(this, data.path)
                }
            }
            break
        case 'OPEN':
            {
                log.statusBarInf('正在打开服务器文件...%s', data.label)
                const clientCacheFile = conn.localCachePath(data.path)
                const logs = progress.Download.File()
                await conn.checkOrUpdateFileCache(data.path, logs.onProgress, false)
                await hx.workspace.openTextDocument(clientCacheFile)
                log.clear()
            }
            break
        case 'OPEN_EXTERNAL':
            {
                const clientCacheFile = conn.localCachePath(data.path)
                if (data.isFile) {
                    const logs = progress.Download.File()
                    await conn.checkOrUpdateFileCache(data.path, logs.onProgress, false)
                } else {
                    await fs.ensureDir(clientCacheFile)
                }
                const open = await import('open')
                open.default(clientCacheFile)
            }
            break
        case 'RENAME':
            await conn.rename(data.path, data.newName)
            break
        case 'COPY_PATH':
            hx.env.clipboard.writeText(data.path)
            break
        case 'COPY':
            const url = util.format('%s://%s%s', 'move', client.host, data.path)
            hx.env.clipboard.writeText(url)
            break
        case 'PASTE':
            {
                const text = await hx.env.clipboard.readText()
                try {
                    const url = new URL(text)
                    switch (url.protocol) {
                        case 'move:':
                            {
                                const { isPath } = await conn.checkUpdateLocalCache(
                                    url.pathname,
                                    true
                                )
                                const localPath = conn.localCachePath(url.pathname)
                                let logs
                                if (isPath) {
                                    logs = progress.Upload.Path()
                                } else {
                                    logs = progress.Upload.File()
                                }
                                await conn.upload(localPath, data.path, logs.onProgress, true)
                            }
                            break
                        case 'file:':
                            {
                                let localPath = url.pathname
                                if (/^\/[a-zA-Z]:\//.test(localPath)) {
                                    localPath = localPath.substring(1)
                                }
                                const stat = await fs.stat(localPath)
                                let logs
                                if (stat.isDirectory()) {
                                    logs = progress.Upload.Path()
                                } else {
                                    logs = progress.Upload.File()
                                }
                                await conn.upload(localPath, data.path, logs.onProgress, true)
                            }
                            break
                    }
                } catch (error) {
                    const name = await file.createFile(
                        '粘贴提示',
                        '检测到您在文件目录中粘贴文本, 是否为此创建文件?\n' + text,
                        '文件名'
                    )
                    if (name) {
                        const remoteFile = posix.join(data.path, name)
                        await conn.writeText(remoteFile, text)
                    }
                }
                await refresh.call(this, data.path)
            }
            break
        case 'SERVER_FILE_SYNC_TO_CLIENT':
            {
                const options = await file.selectSyncFile(
                    '服务器文件同步到本地',
                    '同步方式是根据文件大小和文件最后修改时间来判断文件是否发生了变更。\n检测到变更后, 通过分块对比的方式, 仅传输文件中变更的部分, 因此具有更快的速度, 且\n更加的节省网络流量。'
                )
                if (options.path) {
                    const changed = await conn.isFileChanged(options.path, data.path)
                    if (changed) {
                        options.chunkSize = parseInt(options.chunkSize) * 1024 * 1024
                        await conn.serverSyncToClient(options.path, data.path, options)
                    }
                }
            }
            break
        case 'CLIENT_FILE_SYNC_TO_SERVER':
            {
                const options = await file.selectSyncFile(
                    '本地文件同步到服务器',
                    '同步方式是根据文件大小和文件最后修改时间来判断文件是否发生了变更。\n检测到变更后, 通过分块对比的方式, 仅传输文件中变更的部分, 因此具有更快的速度, 且\n更加的节省网络流量。'
                )
                if (options.path) {
                    const changed = await conn.isFileChanged(options.path, data.path)
                    if (changed) {
                        options.chunkSize = parseInt(options.chunkSize) * 1024 * 1024
                        await conn.clientSyncToServer(options.path, data.path, options)
                    }
                }
            }
            break
        case 'GZIP':
            await conn.zip(data.path)
            break
        case 'UNGZIP':
            await conn.unzip(data.path)
            break
        case 'OPEN_CONTROLLER':
            viewManager.open('controller', host, this.options)
            break
        case 'OPEN_DOCKER':
            viewManager.open('docker', host, this.options)
            break
        case 'MOVE':
            {
                const serverPath = await hx.window.showInputBox({
                    prompt: '请输入目标路径',
                    password: false,
                    value: ''
                })
                if (serverPath) {
                    await conn.move(data.path, serverPath)
                    await refresh.call(this, serverPath)
                }
            }
            break
        case 'MOVE_DRAG':
            {
                const tip = data.isFile ? '文件' : '文件夹'
                const msg = util.format(
                    '将此%s %s 移动到以下位置吗?\n%s',
                    tip,
                    data.sourcePath,
                    data.targetPath
                )
                const button = await hx.window.showMessageBox({
                    type: 'question',
                    title: '移动',
                    text: msg,
                    buttons: ['确认', '取消']
                })
                response = button == '确认'
                if (response) {
                    const sourceParent = dirname(data.sourcePath)
                    if (sourceParent != data.targetPath) {
                        await conn.move(data.sourcePath, data.targetPath)
                    }
                }
            }
            break
        case 'DOWNLOAD_FILE':
            {
                const savePath = await file.selectPath('选择保存位置')
                if (savePath) {
                    const localFile = join(savePath, data.label)
                    const logs = progress.Download.File()
                    await conn.downloadFile(localFile, data.path, logs.onProgress, true)
                    logs.showDetail()
                }
            }
            break
        case 'DOWNLOAD_PATH':
            {
                const savePath = await file.selectPath('选择保存位置')
                if (savePath) {
                    const localFile = join(savePath, data.label)
                    const logs = progress.Download.Path()
                    await conn.downloadFolder(localFile, data.path, logs.onProgress, true)
                    logs.showDetail()
                }
            }
            break
        case 'UPLOAD_FILE':
            {
                const clientFile = await file.selectFile('选择文件', '请选择需要上传到服务器的文件')
                if (clientFile) {
                    const name = basename(clientFile)
                    const serverFile = join(data.isFile ? data.parentPath : data.path, name)
                    const logs = progress.Upload.File()
                    await conn.uploadFile(clientFile, serverFile, logs.onProgress, true)
                    logs.showDetail()
                }
            }
            break
        case 'UPLOAD_PATH':
            {
                const clientPath = await file.selectPath('选择目录', '请选择需要上传到服务器的目录')
                if (clientPath) {
                    const name = basename(clientPath)
                    const serverPath = join(data.isFile ? data.parentPath : data.path, name)
                    const logs = progress.Upload.Path()
                    await conn.uploadFolder(clientPath, serverPath, logs.onProgress, true)
                    logs.showDetail()
                }
            }
            break
        case 'CREATE_SHORTCUT':
            {
                log.statusBarInf('正在创建快捷方式...%s', data.path)
                const shortcutPath = await conn.symlink(data.path, '/')
                await refresh.call(this, '/')
                log.statusBarInf('创建快捷方式完成...%s', shortcutPath)
            }
            break
        case 'SERVER_SHORTCUT':
            {
                log.statusBarInf('正在创建快捷方式...')
                const url =
                    'hbuilderx://requestExtension/fubowen-server-manager/create-connection?host=' +
                    host
                const name = this.options.name ? this.options.name + ' ● ' + host : host
                await os.createShortcutToDesktop(name, url, {
                    icon: 'D:\\utils\\linux-publish\\src\\public\\icons\\windows\\204.ico'
                })
                log.notice.info('创建快捷方式完成')
            }
            break
        case 'OPEN_TERMINAL':
            {
                await viewManager.open('shell', host, this.options)
            }
            break
        case 'OPEN_PORT_FORWARD':
            {
                await viewManager.open('port-forward', host, this.options)
            }
            break
        case 'CREATE_KEYPAIR':
            {
                await viewManager.open('create-keypair')
            }
            break
        case 'SET_LOGIN_KEYPAIR':
            {
                await viewManager.open('select-keypart', host, this.options)
            }
            break
        case 'CLOSE_SERVER':
            {
                await disconnect(host)
            }
            break
        case 'SET_UPLOAD_MENU':
            {
                const id = 'upload_' + host
                const name = '上传到 - ' + this.options.name || host
                const command = util.format(
                    'cmd /c start \\"\\" \\"hbuilderx://requestExtension/fubowen-server-manager/upload?host=%s"&"path=%V\\"',
                    host
                )
                const icon = 'E:\\HBuilderX\\HBuilderX.exe'
                await os.createFileMenu(id, name, command, icon)
                await os.createPathMenu(id, name, command, icon)
                await os.createBackgroundPathMenu(id, name, command, icon)
                log.notice.info('创建右键菜单完成')
            }
            break
        case 'DEL_UPLOAD_MENU':
            {
                const id = 'upload_' + host
                await os.deleteFileMenu(id)
                await os.deletePathMenu(id)
                await os.deleteBackgroundPathMenu(id)
                log.notice.info('删除右键菜单完成')
            }
            break
        case 'ZIP_UPLOAD_FILE':
            {
                const clientFile = await file.selectFile('选择文件', '请选择需要上传到服务器的文件')
                if (clientFile) {
                    const logs = progress.Upload.File()
                    const tgzPath = clientFile + '.tar.gz'
                    logs.logger.inf('开始压缩...%s', clientFile)
                    try {
                        await compressing.tgz.compressFile(clientFile, tgzPath)
                        logs.logger.inf('压缩完成...%s', tgzPath)
                    } catch (error) {
                        logs.logger.err('压缩失败, 结束上传, %s', error.message || '未知错误')
                        throw error
                    }
                    const name = basename(clientFile) + '.tar.gz'
                    const serverFile = posix.join(data.isFile ? data.parentPath : data.path, name)
                    await conn.uploadFile(tgzPath, serverFile, logs.onProgress, true)
                    logs.logger.inf('服务器开始解压...%s', serverPath)
                    try {
                        await conn.unzip(serverFile, true)
                        logs.logger.inf('服务器解压完成...%s', serverPath)
                    } catch (error) {
                        logs.logger.err('解压失败, 请手动解压, %s', error.message || '未知错误')
                    }
                    await fs.remove(tgzPath)
                    logs.logger.inf('上传完毕, 清理资源完成')
                    logs.showDetail()
                }
            }
            break
        case 'ZIP_UPLOAD_PATH':
            {
                const clientPath = await file.selectPath('选择目录', '请选择需要上传到服务器的目录')
                if (clientPath) {
                    const logs = progress.Upload.File()
                    const tgzPath = clientPath + '.tar.gz'
                    logs.logger.inf('开始压缩...%s', clientPath)
                    try {
                        await compressing.tgz.compressDir(clientPath, tgzPath)
                        logs.logger.inf('压缩完成...%s', tgzPath)
                    } catch (error) {
                        logs.logger.err('压缩失败, 结束上传, %s', error.message || '未知错误')
                        throw error
                    }
                    const name = basename(clientPath) + '.tar.gz'
                    const serverPath = posix.join(data.isFile ? data.parentPath : data.path, name)
                    await conn.uploadFile(tgzPath, serverPath, logs.onProgress, true)
                    logs.logger.inf('服务器开始解压...%s', serverPath)
                    try {
                        await conn.unzip(serverPath, true)
                        logs.logger.inf('服务器解压完成...%s', serverPath)
                    } catch (error) {
                        logs.logger.err('解压失败, 请手动解压, %s', error.message || '未知错误')
                    }
                    await fs.remove(tgzPath)
                    logs.logger.inf('上传完毕, 清理资源完成')
                    logs.showDetail()
                }
            }
            break
    }
    return response
}

module.exports = {
    onInit,
    onLoad,
    onContentChange,
    onRemoveCache,
    onCommand
}
